use std::net::{IpAddr, SocketAddr, ToSocketAddrs};

use base64::{Engine, engine::general_purpose::STANDARD as B64};
use http_body_util::Full;
use hyper::{
  Request, Response, StatusCode,
  body::{Body, Bytes},
  header,
  upgrade::Upgraded,
};
use hyper_util::rt::TokioIo;
use rand::Rng;
use tokio::net::TcpSocket;

use crate::resp;

async fn tunnel(
  ipv6: u128,
  prefix_len: u8,
  upgraded: &mut Upgraded,
  addr_str: String,
) -> std::io::Result<()> {
  if let Ok(addrs) = addr_str.to_socket_addrs() {
    for addr in addrs {
      let socket = TcpSocket::new_v6()?;
      let bind_addr = rand_ipv6_socket_addr(ipv6, prefix_len);
      if xerr::is_ok!(socket.bind(bind_addr)) {
        log::info!("{addr_str} → {bind_addr}");
        if let Ok(mut server) = xerr::ok!(socket.connect(addr).await) {
          let mut upgraded = TokioIo::new(upgraded);
          tokio::io::copy_bidirectional(&mut upgraded, &mut server).await?;
          return Ok(());
        }
      }
    }
  } else {
    log::error!("ipv6 tunnel error: {addr_str}");
  }

  Ok(())
}

fn rand_ipv6_socket_addr(ipv6: u128, prefix_len: u8) -> SocketAddr {
  let mut rng = rand::rng();
  SocketAddr::new(
    rand_ipv6(ipv6, prefix_len),
    rng.random_range(1025..u16::MAX),
  )
}

fn rand_ipv6(mut ipv6: u128, prefix_len: u8) -> IpAddr {
  // 避免第一个 IP 被绑定了端口
  let rand: u128 = rand::rng().random_range(2..u128::MAX);
  let net_part = (ipv6 >> (128 - prefix_len)) << (128 - prefix_len);
  let host_part = (rand << prefix_len) >> prefix_len;
  ipv6 = net_part | host_part;
  IpAddr::V6(ipv6.into())
}

async fn process_connect(ipv6: u128, prefix_len: u8, req: Request<impl Body>) {
  let remote_addr = req.uri();
  if let Some(remote_addr) = remote_addr.authority().map(|auth| auth.to_string()) {
    let upgrade = hyper::upgrade::on(req);
    tokio::task::spawn(async move {
      if let Ok(mut upgraded) = xerr::ok!(upgrade.await) {
        xerr::log!(tunnel(ipv6, prefix_len, &mut upgraded, remote_addr).await);
      }
    });
  }
}

pub async fn proxy(
  ipv6: u128,
  prefix_len: u8,
  user_passwd: &[u8],
  mut req: Request<impl Body>,
) -> aok::Result<Response<Full<Bytes>>> {
  if let Some(auth) = req.headers_mut().remove(header::PROXY_AUTHORIZATION) {
    let auth = auth.to_str()?;
    if let Some(p) = auth.find(' ')
      && let Ok(auth) = xerr::ok!(B64.decode(&auth[p + 1..]))
      && user_passwd == &auth[..]
    {
      process_connect(ipv6, prefix_len, req).await;
      return resp(StatusCode::OK);
    }
  }

  resp(StatusCode::UNAUTHORIZED)
}
